#include "Pipe.hpp"
#include "MessageQueue.hpp"
#include "Win32Exception.hpp"

#include <windows.h>
#include <assert.h>
#include <sstream>
#include <iostream>

namespace EGlib
{
  PipeBase::PipeBase(bool is_write_pipe)
  {
    read_pipe = 0;
    write_pipe = 0;
    overlapped_io_done_event = 0;
    overlapped = 0;

    try
    {
      static int pipe_index = 0;
      std::wstringstream name_str;
      name_str << "\\\\.\\pipe\\EGlib_" << GetCurrentProcessId() << "_" << pipe_index++;

      HANDLE server_pipe = CreateNamedPipe(name_str.str().c_str(),
        PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT,
        PIPE_UNLIMITED_INSTANCES,
        4096,4096,NMPWAIT_USE_DEFAULT_WAIT,NULL);
      if(server_pipe == INVALID_HANDLE_VALUE)
        throw Win32Exception::get_last_error();

      SECURITY_ATTRIBUTES security_attrs;
      security_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);
      security_attrs.bInheritHandle = true;
      security_attrs.lpSecurityDescriptor = NULL;
      HANDLE client_pipe = CreateFile(name_str.str().c_str(),
        is_write_pipe ? GENERIC_READ : GENERIC_WRITE,
        0,&security_attrs,OPEN_EXISTING,0,NULL);
      if(client_pipe == INVALID_HANDLE_VALUE)
        throw Win32Exception::get_last_error();

      if(is_write_pipe)
      {
        write_pipe = server_pipe;
        read_pipe = client_pipe;
      }
      else
      {
        read_pipe = server_pipe;
        write_pipe = client_pipe;
      }

      overlapped_io_done_event = CreateEvent(NULL,TRUE,TRUE,NULL);
      if(overlapped_io_done_event == INVALID_HANDLE_VALUE)
        throw Win32Exception::get_last_error();

      overlapped = new OVERLAPPED;
      memset(overlapped,0,sizeof(OVERLAPPED));
    }
    catch(...)
    {
      cleanup();
      throw;
    }
  }

  PipeBase::~PipeBase()
  {
    cleanup();
  }

  void PipeBase::cleanup()
  {
    if(read_pipe)
    {
      CancelIo(read_pipe);

      CloseHandle(read_pipe);
      read_pipe = 0;
    }

    if(write_pipe)
    {
      CancelIo(write_pipe);

      CloseHandle(write_pipe);
      write_pipe = 0;
    }

    if(overlapped_io_done_event)
    {
      CloseHandle(overlapped_io_done_event);
      overlapped_io_done_event = 0;
    }

    delete overlapped;
  }

  ////////
  // WritePipe
  void WritePipe::write_overlapped(void *buffer,int size)
  {
    assert(WaitForSingleObject(overlapped_io_done_event,0) == WAIT_OBJECT_0);

    memset(overlapped,0,sizeof(OVERLAPPED));
    overlapped->hEvent = overlapped_io_done_event;

    BOOL res = WriteFile(write_pipe,buffer,size,NULL,overlapped);
    if(!res && GetLastError() != ERROR_IO_PENDING)
      throw Win32Exception::get_last_error();
  }

  ////////
  // ReadPipe
  void ReadPipe::read_overlapped(void *dest,int max_size)
  {
    assert(WaitForSingleObject(overlapped_io_done_event,0) == WAIT_OBJECT_0);

    memset(overlapped,0,sizeof(OVERLAPPED));
    overlapped->hEvent = overlapped_io_done_event;

    BOOL res = ReadFile(read_pipe,dest,max_size,NULL,overlapped);
    if(!res && GetLastError() != ERROR_IO_PENDING)
      throw Win32Exception::get_last_error();
  }

  int ReadPipe::get_read_size()
  {
    assert(WaitForSingleObject(overlapped_io_done_event,0) == WAIT_OBJECT_0);

    DWORD num_bytes_read;
    BOOL res = GetOverlappedResult(read_pipe,overlapped,&num_bytes_read,false);
    if(res)
      return (int)num_bytes_read;
    else
    {
      if(GetLastError() == ERROR_HANDLE_EOF)
        return -1;
      else
        return 0;
    }
  }
}
